package org.orbis.memoryblock.invert;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.orbis.communicate.FlagsResult;
import org.orbis.communicate.indexer.term.TermPackage;
import org.orbis.constants.Constants;
import org.orbis.memoryblock.MemoryBlock;
import org.orbis.structure.LookupPointer;
import org.orbis.toolbox.ByteConverter;

/** Implements skip pointer for content.<br>
 * Data structure as follows:
 * <dl>
 * <dt>[Block]</dt>
 * <dd> --&gt; &lt;[Newest] &lt;[Bool flag]&lt;[Int. flag]&gt;* &lt;[Long flag]&gt;* &lt;[Float flag]&gt;* [ID]&gt;<sup>100</sup>&gt;*</dd>
 * <dt>[Newest]</dt>
 * <dd> --&gt; (2-byte digits, represents index of latest written)</dd>
 * <dt>[Bool flag]</dt>
 * <dd> --&gt; (some bytes, each bit represents whether the flag is on or off)</dd>
 * <dt>[Int flag]</dt>
 * <dd> --&gt; (4-byte digits)</dd>
 * <dt>[Long flag]</dt>
 * <dd> --&gt; (8-byte digits)</dd>
 * <dt>[Float flag]</dt>
 * <dd> --&gt; (4-byte floating point number)</dd>
 * <dt>[ID]</dt>
 * <dd> --&gt; (8-byte digits)</dd>
 * </dl>
 * 
 * @author iamyusk */
public class InvertIndexBlock extends MemoryBlock{
	public static final int OPT_LEN = 0;
	public static final int OPT_TERMNUM = 1;
	public static final int OPT_BOOLFLAG = 2;
	public static final int OPT_INTFLAG = 3;
	public static final int OPT_BOOLARR = 4;
	public static final int OPT_UNIQUE = 5;
	public static final int OPT_LONGFLAG = 6;
	public static final int OPT_FLOATFLAG = 7;
	public static final int OPTSIZE = 8;
	private int perID = 0;

	public InvertIndexBlock(ByteBuffer block, int bodySize, int ID, int repBlock){
		super(block, bodySize, ID, Constants.MEMORY_TYPE_INVINDEX, repBlock);
	}

	public InvertIndexBlock(ByteBuffer existingBody){
		super(existingBody);
	}

	public FlagsResult[] read(int ptr){
		int newest = getInt(ptr, NEWEST); // The maximum value to be written
		ptr += NEWEST;
		int lastPtr = ptr + (option[OPT_TERMNUM] * perID) - LONG; // Pointer at option[OPT_PERTERM]
		// check whether it reads all or not
		int readLength = option[OPT_TERMNUM];
		if(getLong(lastPtr, LONG) == 0L) readLength = newest;

		FlagsResult[] pkgs = new FlagsResult[readLength];
		for(int i = 0; i < readLength; i++){
			pkgs[i] =
				new FlagsResult(option[OPT_BOOLFLAG], option[OPT_INTFLAG], option[OPT_LONGFLAG],
					option[OPT_FLOATFLAG]);
			boolean[] binaryFlags =
				TermPackage.byteToFlag(read(ptr, option[OPT_BOOLARR]), option[OPT_BOOLFLAG]);
			ptr += option[OPT_BOOLARR];
			pkgs[i].setBinaryFlags(binaryFlags);

			int[] intFlags = new int[option[OPT_INTFLAG]];
			for(int j = 0; j < option[OPT_INTFLAG]; j++){
				intFlags[j] = getInt(ptr, INT);
				ptr += INT;
			}
			pkgs[i].setIntFlags(intFlags);

			long[] longFlags = new long[option[OPT_LONGFLAG]];
			for(int j = 0; j < option[OPT_LONGFLAG]; j++){
				longFlags[j] = getLong(ptr, LONG);
				ptr += LONG;
			}
			pkgs[i].setLongFlags(longFlags);

			float[] floatFlags = new float[option[OPT_FLOATFLAG]];
			for(int j = 0; j < option[OPT_FLOATFLAG]; j++){
				floatFlags[j] = getFloat(ptr);
				ptr += FLOAT;
			}
			pkgs[i].setFloatFlags(floatFlags);

			long val = getLong(ptr, LONG);

			pkgs[i].setID(val);
			ptr += LONG;
		}

		return pkgs;
	}

	private boolean renumber(int ptr, long ID, int[] keys){
		if(option[OPT_UNIQUE] < 0) return false;
		if(keys.length == 0) return false;
		ptr += NEWEST;
		for(int i = 0; i < option[OPT_TERMNUM]; i++){
			ptr += option[OPT_BOOLARR] + (INT * option[OPT_UNIQUE]);
			int comp = getInt(ptr, INT);
			ptr += INT * (option[OPT_INTFLAG] - option[OPT_UNIQUE]);
			if(keys[option[OPT_UNIQUE]] == comp){
				setLong(ptr, LONG, ID);
				return true;
			}
			ptr += LONG;
		}

		return false;
	}

	public LookupPointer write(int ptr, TermPackage pkg){
		int newest = getInt(ptr, NEWEST);
		LookupPointer stored = LookupPointer.valueOf(getID(), ptr);
		// Increment newest counter
		newest = (newest == option[OPT_TERMNUM] - 1? 0: newest + 1);
		// System.out.println("[DEBUG]Write to number " + newest + " ptr: " + ptr);
		setInt(ptr, NEWEST, newest);

		if(!renumber(ptr, pkg.ID(), pkg.intFlags())){
			ptr += NEWEST + ((newest == 0? option[OPT_TERMNUM] - 1: newest - 1) * perID);
			stored = LookupPointer.valueOf(getID(), ptr);
			store(TermPackage.flagToByte(pkg.binaryFlags()), ptr);
			ptr += option[OPT_BOOLARR];
			for(int i = 0; i < option[OPT_INTFLAG]; i++){
				setInt(ptr, INT, pkg.intFlag(i));
				ptr += INT;
			}
			for(int i = 0; i < option[OPT_LONGFLAG]; i++){
				setLong(ptr, LONG, pkg.longFlag(i));
				ptr += LONG;
			}
			for(int i = 0; i < option[OPT_FLOATFLAG]; i++){
				setFloat(ptr, pkg.floatFlag(i));
				ptr += FLOAT;
			}

			setLong(ptr, LONG, pkg.ID());
		}

		return stored;
	}

	public void resetPointer(int ptr, LookupPointer newPtr){
		store(newPtr.toBytes(), ptr);
	}

	public synchronized int allocate(){
		int newSize = NEWEST; // newest director
		newSize += (option[OPT_TERMNUM] * perID);
		if(bodyVacancy < newSize) return -1;
		bodyVacancy -= newSize;
		option[OPT_LEN]++;
		return bodySize - bodyVacancy - newSize;
	}

	public int length(){
		return NEWEST + (option[OPT_TERMNUM] * perID);
	}

	@Override
	public int getOptionSize(){
		return OPTSIZE;
	}

	@Override
	public byte[] getOptionByteStream(){
		ByteArrayOutputStream stream = new ByteArrayOutputStream();

		for(int i = 0; i < OPTSIZE; i++){
			try{
				stream.write(ByteConverter.intToByte(option[i], 4));
			}catch(IOException e){}
		}

		return stream.toByteArray();
	}

	@Override
	public int getOption(int index){
		if(index >= OPTSIZE) return -1;
		return option[index];
	}

	@Override
	public void setOption(int index, int value){
		if(index == OPT_BOOLARR || index >= OPTSIZE) return;
		option[index] = value;
		if(index == OPT_BOOLFLAG){
			if(value == 0) option[OPT_BOOLARR] = 0;
			else option[OPT_BOOLARR] = (int)Math.ceil(0.125 * value);
		}
		this.perID =
			option[OPT_BOOLARR] + (option[OPT_INTFLAG] * INT) + (option[OPT_LONGFLAG] * LONG)
				+ (option[OPT_FLOATFLAG] * FLOAT) + LONG;
	}

	@Override
	public String getClassName(){
		return new String("InvertIndex");
	}
}
